#include <cstdio>
#include <cstdlib>

// Included files for OpenGL Rendering
#ifdef __APPLE__
#include <OpenGL/gl.h>
#else
#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glu.h>
#endif

#include "radiosity.h"
#include "mesh.h"
#include "face.h"
#include "glCanvas.h"
#include "sphere.h"
#include "raytree.h"
#include "raytracer.h"
#include "utils.h"

// ================================================================
// CONSTRUCTOR & DESTRUCTOR
// ================================================================
Radiosity::Radiosity(Mesh *m, ArgParser *a) {
  mesh = m;
  args = a;
  num_faces = -1;  
  formfactors = NULL;
  area = NULL;
  undistributed = NULL;
  absorbed = NULL;
  radiance = NULL;
  max_undistributed_patch = -1;
  total_area = -1;
  Reset();
}

Radiosity::~Radiosity() {
  Cleanup();
}

void Radiosity::Cleanup() {
  delete [] formfactors;
  delete [] area;
  delete [] undistributed;
  delete [] absorbed;
  delete [] radiance;
  num_faces = -1;
  formfactors = NULL;
  area = NULL;
  undistributed = NULL;
  absorbed = NULL;
  radiance = NULL;
  max_undistributed_patch = -1;
  total_area = -1;
}

void Radiosity::Reset() {
  delete [] area;
  delete [] undistributed;
  delete [] absorbed;
  delete [] radiance;

  // create and fill the data structures
  num_faces = mesh->numFaces();
  area = new double[num_faces];
  undistributed = new Vec3f[num_faces];
  absorbed = new Vec3f[num_faces];
  radiance = new Vec3f[num_faces];
  for (int i = 0; i < num_faces; i++) {
    Face *f = mesh->getFace(i);
    f->setRadiosityPatchIndex(i);
    setArea(i,f->getArea());
    Vec3f emit = f->getMaterial()->getEmittedColor();
    setUndistributed(i,emit);
    setAbsorbed(i,Vec3f(0,0,0));
    setRadiance(i,emit);
  }

  // find the patch with the most undistributed energy
  findMaxUndistributed();
}

// =======================================================================================
// =======================================================================================

void Radiosity::findMaxUndistributed()
{
}


void Radiosity::ComputeFormFactors()
{
}

// ================================================================
// ================================================================

double Radiosity::Iterate()
{
	return 0.0;
}

// =======================================================================
// PAINT
// =======================================================================

Vec3f Radiosity::whichVisualization(enum RENDER_MODE mode, Face *f, int i) {
  assert (mesh->getFace(i) == f);
  assert (i >= 0 && i < num_faces);
  if (mode == RENDER_LIGHTS) {
    return f->getMaterial()->getEmittedColor();
  } else if (mode == RENDER_UNDISTRIBUTED) { 
    return getUndistributed(i);
  } else if (mode == RENDER_ABSORBED) {
    return getAbsorbed(i);
  } else if (mode == RENDER_RADIANCE) {
    return getRadiance(i);
  } else if (mode == RENDER_FORM_FACTORS) {
    if (formfactors == NULL) ComputeFormFactors();
    double scale = 0.2 * total_area/getArea(i);
    double factor = scale * getFormFactor(max_undistributed_patch,i);
    return Vec3f(factor,factor,factor);
  } else {
    assert(0);
  }
  exit(0);
}


// for interpolation
void CollectFacesWithVertex(Vertex *have, Face *f, std::vector<Face*> &faces) {
  for (unsigned int i = 0; i < faces.size(); i++) {
    if (faces[i] == f) return;
  }
  if (have != (*f)[0] && have != (*f)[1] && have != (*f)[2] && have != (*f)[3]) return;
  faces.push_back(f);
  for (int i = 0; i < 4; i++) {
    Edge *ea = f->getEdge()->getOpposite();
    Edge *eb = f->getEdge()->getNext()->getOpposite();
    Edge *ec = f->getEdge()->getNext()->getNext()->getOpposite();
    Edge *ed = f->getEdge()->getNext()->getNext()->getNext()->getOpposite();
    if (ea != NULL) CollectFacesWithVertex(have,ea->getFace(),faces);
    if (eb != NULL) CollectFacesWithVertex(have,eb->getFace(),faces);
    if (ec != NULL) CollectFacesWithVertex(have,ec->getFace(),faces);
    if (ed != NULL) CollectFacesWithVertex(have,ed->getFace(),faces);
  }
}

void Radiosity::insertColor(Vec3f v) {
  double r = linear_to_srgb(v.x());
  double g = linear_to_srgb(v.y());
  double b = linear_to_srgb(v.z());
  glColor3f(r,g,b);
}

void Radiosity::insertInterpolatedColor(int index, Face *f, Vertex *v) {
  std::vector<Face*> faces;
  CollectFacesWithVertex(v,f,faces);
  double total = 0;
  Vec3f color = Vec3f(0,0,0);
  Vec3f normal = f->computeNormal();
  for (unsigned int i = 0; i < faces.size(); i++) {
    Vec3f normal2 = faces[i]->computeNormal();
    double area = faces[i]->getArea();
    if (normal.Dot3(normal2) < 0.5) continue;
    assert (area > 0);
    total += area;
    color += area * whichVisualization(RENDER_RADIANCE,faces[i],faces[i]->getRadiosityPatchIndex());
  }
  assert (total > 0);
  color /= total;
  insertColor(color);
}

void Radiosity::Paint(ArgParser *args)
{
	// this offset prevents "z-fighting" bewteen the edges and faces
	// the edges will always win.
	glEnable(GL_POLYGON_OFFSET_FILL);
	glPolygonOffset(1.1,4.0);
  
	// draw the faces with OpenGL lighting, just to understand the geometry
	// (the GL light has nothing to do with the surfaces that emit light!)
	for ( int i = 0; i < num_faces; i++)
	{
		Face *f = mesh->getFace(i);
		Material *m = f->getMaterial();

		Vec3f a = (*f)[0]->get();
		Vec3f b = (*f)[1]->get();
		Vec3f c = (*f)[2]->get();
		Vec3f d = (*f)[3]->get();
		Vec3f normal = f->computeNormal();
		glNormal3f(normal.x(),normal.y(),normal.z());

		if (!m->hasTextureMap())
		{
			Vec3f color = m->getDiffuseColor();
			insertColor(color);
			glBegin (GL_QUADS);
			glVertex3f(a.x(),a.y(),a.z());
			glVertex3f(b.x(),b.y(),b.z());
			glVertex3f(c.x(),c.y(),c.z());
			glVertex3f(d.x(),d.y(),d.z());
			glEnd();
		}
		else
		{
			glEnable(GL_TEXTURE_2D);
			glColor3f(1,1,1);
			glBindTexture(GL_TEXTURE_2D,m->getTextureID());
			glBegin (GL_QUADS);
			glTexCoord2d((*f)[0]->get_s(),(*f)[0]->get_t()); 
			glVertex3f(a.x(),a.y(),a.z());
			glTexCoord2d((*f)[1]->get_s(),(*f)[1]->get_t()); 
			glVertex3f(b.x(),b.y(),b.z());
			glTexCoord2d((*f)[2]->get_s(),(*f)[2]->get_t()); 
			glVertex3f(c.x(),c.y(),c.z());
			glTexCoord2d((*f)[3]->get_s(),(*f)[3]->get_t()); 
			glVertex3f(d.x(),d.y(),d.z());
			glEnd();
			glDisable(GL_TEXTURE_2D);	
		}
	}

	glDisable(GL_POLYGON_OFFSET_FILL); 
	HandleGLError(); 
  
	if (args->wireframe) {
	mesh->PaintWireframe(); 
	}
	}

